from datetime import datetime
from re import compile
from math import pow
import libxml2
from duetopia.rdf.xml import RdfDoc, findChild
from error import *

__all__ = ["EnvisatRdfDoc", "newEnvisatRdfDoc"]

class EnvisatRdfDoc(RdfDoc):
    """
    RDF Document, implementing specific handlers for numeric
    scientific-numeric data types for EO data.   
    See also the RdfDoc class in duetopia.rdf.xml. 
    Requires a configuration file with a [namespaces] section.
    """
    
    def numNode(self, ns, name, base=10):
        """
        Produces a literal numeric value from several formats
    
        >>> testset = [
        ...     "3",
        ...     "+030",
        ...     "-40<bytes>",
        ...     "-.456044<s>",
        ...     "-0077423098<10-6degN>",
        ...     "+0.38000000e+12<%>",
        ... ]
        >>> doc = newEnvisatRdfDoc("1.0", TestConfig)
        >>> f = doc.numNode("envisat", "testNum")
        >>> for n in testset:
        ...     f(doc.root, n)
        ...
        >>> print doc.serialize("utf-8", 1)
        <?xml version="1.0" encoding="utf-8"?>
        <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:envisat="http://www.example.com/schemas/envisat.rdf#">
          <envisat:testNum>3</envisat:testNum>
          <envisat:testNum>30</envisat:testNum>
          <envisat:testNum>-40</envisat:testNum>
          <envisat:testNumUnits>bytes</envisat:testNumUnits>
          <envisat:testNum>-0.456044</envisat:testNum>
          <envisat:testNumUnits>s</envisat:testNumUnits>
          <envisat:testNum>-77.423098</envisat:testNum>
          <envisat:testNumUnits>degN</envisat:testNumUnits>
          <envisat:testNum>380000000000.0</envisat:testNum>
          <envisat:testNumUnits>%</envisat:testNumUnits>
        </rdf:RDF>
        <BLANKLINE>
        """
        rexp = "(?P<num>[0-9]+(\.[0-9]+){0,1})" + \
            "([eE](?P<exp>[+-][0-9]+)){0,1}" + \
            "(<((?P<base>[0-9]+)(?P<ord>[+-][0-9]+)){0,1}(?P<units>.*)>){0,1}$"
        rexpc = compile(rexp)
        def _f(parent, s):
            ## find sign
            if s[0] == '+':
                sign = 1
                s = s[1:]
            elif s[0] == '-': 
                sign = -1
                s = s[1:]
            else:
                sign = 1
            ## sometimes leading .
            if s[0] == '.':
                s = "0" + s
            ## parse out any exponents and units
            m = rexpc.match(s)
            if not m:
                raise FormatError("%s: %s" % (name, s))
            parts = m.groupdict()
            try:
                num = sign * int(parts["num"], base)
            except ValueError:
                num = sign * float(parts["num"])
    
            ## calculate order of magnitude if present with units
            if parts["base"] and parts["ord"]:
                ord = pow(int(parts["base"]), int(parts["ord"]))
                num = ord * num
    
            ## calculate value in scientific notation if we have an exponent
            if parts["exp"]:
                exp = pow(10, int(parts["exp"]))
                num = exp * num
    
            node = self.newNsNode(ns, name)
            node.setContent("%s" % (num))
            parent.addChild(node)
            if parts["units"]:
                units = self.newNsNode(ns, name + "Units")
                units.setContent(parts["units"])
                parent.addChild(units)
        return _f

    def multiNumNode(self, ns, name):
        """
        Produces a sequence of numbers, all with the same (optional) units
        
        >>> def pp(nodes):
        ...     for n in nodes:
        ...         print n.serialize("utf-8", 1)
        ...
        >>> testset = [
        ...     "+0000412691+0000442559+0000489882+0000509819+0000559694+0000619601+0000664573+0000680821+0000708329+0000753371+0000761508+0000778409+0000864876+0000884944+0000900000<10-3nm>",
        ...    "-6.45519327E-01-1.18825907E-01-7.54443638E-01",
        ...    ]
        >>> doc = newEnvisatRdfDoc("1.0", TestConfig)
        >>> f = doc.multiNumNode("envisat", "testNum")
        >>> for n in testset:
        ...     f(doc.root, n)
        ...
        >>> print doc.serialize("utf-8", 1)
        <?xml version="1.0" encoding="utf-8"?>
        <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:envisat="http://www.example.com/schemas/envisat.rdf#">
          <envisat:testNum>
            <rdf:Seq>
              <rdf:li envisat:value="412.691"/>
              <rdf:li envisat:value="442.559"/>
              <rdf:li envisat:value="489.882"/>
              <rdf:li envisat:value="509.819"/>
              <rdf:li envisat:value="559.694"/>
              <rdf:li envisat:value="619.601"/>
              <rdf:li envisat:value="664.573"/>
              <rdf:li envisat:value="680.821"/>
              <rdf:li envisat:value="708.329"/>
              <rdf:li envisat:value="753.371"/>
              <rdf:li envisat:value="761.508"/>
              <rdf:li envisat:value="778.409"/>
              <rdf:li envisat:value="864.876"/>
              <rdf:li envisat:value="884.944"/>
              <rdf:li envisat:value="900.0"/>
            </rdf:Seq>
          </envisat:testNum>
          <envisat:testNumUnits>nm</envisat:testNumUnits>
        </rdf:RDF>
        <BLANKLINE>
        """
        numu_re = "^(?P<nums>[0-9+-]+)" + \
            "(XXX[eE](?P<exp>[+-][0-9]+)){0,1}" + \
            "(<((?P<base>[0-9]+)(?P<ord>[+-][0-9]+)){0,1}(?P<units>.*)>){0,1}$"
        numu_rec = compile(numu_re)
        numsp_re = "^([+-][0-9]+)(.*)$"
        numsp_rec = compile(numsp_re)
        def _f(parent, s):
            ## first separate the numbers from the exponent and units
            m = numu_rec.match(s)
            if not m:
                raise FormatError("%s: %s" % (name, s))
            parts = m.groupdict()

            if parts["base"] and parts["ord"]:
                ord = pow(int(parts["base"]), int(parts["ord"]))
            else:
                ord = 1

            ## calculate value in scientific notation if we have an exponent
            if parts["exp"]:
                exp = pow(10, int(parts["exp"]))
            else:
                exp = 1
    
    
            node = self.newNsNode(ns, name)
            seq = self.newNsNode("rdf", "Seq")
            node.addChild(seq)
    
            ## now get the number list and populate the return list
            tail = parts["nums"]
            while True:
                if not tail:
                    break
                m = numsp_rec.match(tail)
                if not m:
                    raise FormatError("%s: %s" % (name, s))
                head, tail = m.groups()
                if head[0] == '-':
                    sign = -1
                    head = head[1:]
                elif head[0] == '+':
                    sign = 1
                    head = head[1:]
                else:
                    sign = 1
                num = exp * ord * sign * int(head)

                item = self.newNsNode("rdf", "li")
                item.setNsProp(self.getNs(ns), "value", "%s" % num)
                seq.addChild(item)

            parent.addChild(node)

            if parts["units"]:
                units = self.newNsNode(ns, name + "Units")
                units.setContent(parts["units"])
                parent.addChild(units)
        return _f
    

def newEnvisatRdfDoc(version, config):
    """
    >>> doc = newEnvisatRdfDoc("1.1", TestConfig)
    >>> print doc
    <?xml version="1.1"?>
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
    <BLANKLINE>
    """
    ret = libxml2.newDoc(version)
    if ret is None:
        raise treeError('newDoc() failed')
    return EnvisatRdfDoc(config, _obj=ret._o)


if __name__ == '__main__':
    from tempfile import mktemp
    from os import unlink
    from sys import argv
    import doctest

    from config import EnvisatConfig
    from optparse import Values

    testconfig = """
[namespaces]
rdf=http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs=http://www.w3.org/2000/01/rdf-schema#
owl=http://www.w3.org/2002/07/owl#
envisat=http://www.example.com/schemas/envisat.rdf#
"""
    options = Values({ 'config' : mktemp() })
    fp = open(options.config, "w+")
    fp.write(testconfig)
    fp.close()
    TestConfig = EnvisatConfig(options)

    doctest.testmod()

    unlink(options.config)
